{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Julia es rápido\n",
    "\n",
    "Frecuentemente, se usan benchmarks para comparar lenguajes. Éstos benchmarks pueden llevar a largas discusiones; primero, para saber lo que se está midiendo y segundo para explicar sus diferencias. Estas preguntas sencillas a veces son mucho más complicadas de lo que uno se imagina.\n",
    "\n",
    "El propósito de este notebook es para que tú puedas hacer un benchmark simple. Uno puede leer el notebook y ver que sucedió en la Macbook Pro 4-core Intel Core i7 del autor, o correrlo uno mismo.\n",
    "\n",
    "(Este material empezó como parte de una clase que dió Steven Johnson en MIT: https://github.com/stevengj/18S096/blob/master/lectures/lecture1/Boxes-and-registers.ipynb.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Esquema de este notebook\n",
    "- Definir la función suma\n",
    "- Implementación y benchmark de la función suma en ...\n",
    "    - C\n",
    "    - python (interno)\n",
    "    - python (numpy)\n",
    "    - python (hecho a mano)\n",
    "    - Julia (interno)\n",
    "    - Julia (hecho a mano)\n",
    "- Resúmenes de resultados"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# `sum`: una función fácil de entender"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Considera la función  **sum** `sum(a)`, la cual calcula\n",
    "$$\n",
    "\\mathrm{sum}(a) = \\sum_{i=1}^n a_i,\n",
    "$$\n",
    "con $n$ longitud de `a`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "a = rand(10^7) # Vector 1D uniforme en [0,1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "sum(a)   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El resultado esperado es 0.5 * 10^7, pues el promedio de cada entrada es .5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Benchmarks de algunas manera en algunos lenguajes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Julia cuenta con el paquete `BenchmarkTools.jl` para hacer benchmarks fácil y rápidos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#Pkg.add(\"BenchmarkTools\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "using BenchmarkTools  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#  1. El lenguaje C\n",
    "\n",
    "C es considerado el estándar dorado: difícil para el usuario, fácil para la máquina. Estar dentro de un factor de 2 de C puede ser muy satisfactorio. Sin emargo, aún dentro de C, existen muchos tipos de optimizaciones posibles que un usuario de C promedio puedo o no aprovechar.\n",
    "\n",
    "Su autor no habla C, entonces no va a leer la celda siguiente, pero será feliz en saber que puedes poner código de C en una sesión de Julia, compilarlo, y correrlo. Nota que `\"\"\"` denota una cadena de varias líneas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "C_code = \"\"\"\n",
    "#include <stddef.h>\n",
    "double c_sum(size_t n, double *X) {\n",
    "    double s = 0.0;\n",
    "    for (size_t i = 0; i < n; ++i) {\n",
    "        s += X[i];\n",
    "    }\n",
    "    return s;\n",
    "}\n",
    "\"\"\"\n",
    "\n",
    "const Clib = tempname()   # Haz un directorio temporario\n",
    "\n",
    "# compila a una biblioteca compartida pipeando C_code a gcc\n",
    "# (funciona sólo con gcc instalado):\n",
    "\n",
    "open(`gcc -fPIC -O3 -msse3 -xc -shared -o $(Clib * \".\" * Libdl.dlext) -`, \"w\") do f\n",
    "    print(f, C_code) \n",
    "end\n",
    "\n",
    "# define una funcion de Julia que llama a la función de C:\n",
    "c_sum(X::Array{Float64}) = ccall((\"c_sum\", Clib), Float64, (Csize_t, Ptr{Float64}), length(X), X)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "c_sum(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "c_sum(a) ≈ sum(a) # teclea \\approx y luego <TAB> para obtener el símbolo ≈"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "≈  # alias para la función `isapprox`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "?isapprox"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¡Ahora podemos correr el benchmark directo desde Julia!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "c_bench = @benchmark c_sum($a) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "println(\"C: Tiempo más rápido fue $(minimum(c_bench.times) / 1e6) msec\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "d = Dict()  # un diccionario a.k.a un arreglo asociativo\n",
    "d[\"C\"] = minimum(c_bench.times) / 1e6  # en milisegundos\n",
    "d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "using Plots\n",
    "gr()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "t = c_bench.times / 1e6 # tiempos en milisegundos\n",
    "m, σ = minimum(t), std(t)\n",
    "\n",
    "histogram(t, bins=500,\n",
    "    xlim=(m - 0.01, m + σ),\n",
    "    xlabel=\"milliseconds\", ylabel=\"count\", label=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. Python y `sum` interno "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El paquete `PyCall` provee un interfaz de Julia a Python"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "#Pkg.add(\"PyCall\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "using PyCall"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Llama una función de bajo nivel de PyCall para obtener una lista de Python\n",
    "# porque por default PyCall convertirá a un arreglo de NumPy en vez (benchmarkeamos NumPy más abajo)\n",
    "\n",
    "apy_list = PyCall.array2py(a, 1, 1)\n",
    "\n",
    "# obtener el \"sum\" interno de Python\n",
    "pysum = pybuiltin(\"sum\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "pysum(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "pysum(a) ≈ sum(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "py_list_bench = @benchmark $pysum($apy_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "d[\"Python interno\"] = minimum(py_list_bench.times) / 1e6\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3. Python: `numpy` \n",
    "\n",
    "## Aprovechar arquitectura \"SIMD\" pero sólo cuando funciona\n",
    "\n",
    "`numpy` es una biblioteca de C optimizada, llamable desde Python, que se puede instalar en Julia haciendo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "using Conda \n",
    "#Conda.add(\"numpy\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "numpy_sum = pyimport(\"numpy\")[\"sum\"]\n",
    "apy_numpy = PyObject(a) # convierte a un arreglo de NumPy por default\n",
    "\n",
    "py_numpy_bench = @benchmark $numpy_sum($apy_numpy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "numpy_sum(apy_list) # python thing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "numpy_sum(apy_list) ≈ sum(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "d[\"Python numpy\"] = minimum(py_numpy_bench.times) / 1e6\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4. Python, hecho a mano"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "py\"\"\"\n",
    "def py_sum(a):\n",
    "    s = 0.0\n",
    "    for x in a:\n",
    "        s = s + x\n",
    "    return s\n",
    "\"\"\"\n",
    "\n",
    "sum_py = py\"py_sum\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "py_hand = @benchmark $sum_py($apy_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "sum_py(apy_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "sum_py(apy_list) ≈ sum(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "d[\"Python hecho a mano\"] = minimum(py_hand.times) / 1e6\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 5. Julia (interno) \n",
    "\n",
    "   ## Escrito directo en Julia, ¡no en C!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "@which sum(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "j_bench = @benchmark sum($a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "d[\"Julia interno\"] = minimum(j_bench.times) / 1e6\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 6. Julia (hecho a mano) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "function mysum(A)   \n",
    "    s = 0.0  # s = zero(eltype(A))\n",
    "    for a in A\n",
    "        s += a\n",
    "    end\n",
    "    s\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "j_bench_hand = @benchmark mysum($a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "d[\"Julia hecho a mano\"] = minimum(j_bench_hand.times) / 1e6\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Summary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for (key, value) in sort(collect(d))\n",
    "    println(rpad(key, 20, \".\"), lpad(round(value, 1), 8, \".\"))\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "for (key, value) in sort(collect(d), by=x->x[2])\n",
    "    println(rpad(key, 20, \".\"), lpad(round(value, 2), 10, \".\"))\n",
    "end"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Julia 0.6.1",
   "language": "julia",
   "name": "julia-0.6"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "0.6.2"
  },
  "toc": {
   "colors": {
    "hover_highlight": "#DAA520",
    "running_highlight": "#FF0000",
    "selected_highlight": "#FFD700"
   },
   "moveMenuLeft": true,
   "nav_menu": {
    "height": "212px",
    "width": "252px"
   },
   "navigate_menu": true,
   "number_sections": true,
   "sideBar": true,
   "threshold": "2",
   "toc_cell": false,
   "toc_section_display": "block",
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
